use anyhow::Result;
use dotenv;

use clap::App;

use hyper::{body, Client, Request, Response};
use sqlx::{mysql::MySqlConnectOptions, types, ConnectOptions, MySqlPool, Row};

use log::LevelFilter;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::str::FromStr;

mod db;
mod miner;
mod pledge;
mod tool;

fn init_log() -> Result<()> {
    fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{}[{}][{}] {}",
                chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"),
                record.target(),
                record.level(),
                message
            ))
        })
        .level(log::LevelFilter::Debug)
        .chain(std::io::stdout())
        .chain(fern::log_file("output.log")?)
        .apply()?;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    dotenv::dotenv().ok();
    init_log();

    let matches = App::new("tool")
        .version("1.0")
        .author("loop")
        .about("sector tools")
        .subcommand(
            App::new("pledge")
                .about("auto pledge sectors")
                .version("0.1.0")
                .arg("-p, --print 'print sector pledge info, sector number and worker'"),
        )
        .subcommand(App::new("data-transfer").version("0.1.0"))
        .subcommand(App::new("db-clean").version("0.1.0"))
        .subcommand(App::new("tools").version("0.1.0"))
        .subcommand(App::new("sector").version("0.1.0"))
        .subcommand(App::new("compare").version("0.1.0"))
        .get_matches();

    if let Some(ref matches) = matches.subcommand_matches("pledge") {
        let database = parse_db()?;
        let (token, miner_url) = parse_miner()?;
        let mut options = MySqlConnectOptions::from_str(&database)?;
        options.disable_statement_logging();
        let pool = MySqlPool::connect_with(options).await?;

        let client = Client::new();

        pledge::auto_pledge_sector(&pool, &client, &miner_url, &token).await?;
    };

    if let Some(ref matches) = matches.subcommand_matches("data-transfer") {
        let database = parse_db()?;
        let (token, miner_url) = parse_miner()?;
        let mut options = MySqlConnectOptions::from_str(&database)?;
        options.disable_statement_logging();
        let pool = MySqlPool::connect_with(options).await?;
        // let _ = tool::transfer_old::parse_worker_online(&pool).await?;
        // let _ = tool::transfer_old::transfer_old_from_sqlite(&pool).await?;
        let _ = tool::transfer_old::update_worker_id(&pool).await?;
    }

    if let Some(ref matches) = matches.subcommand_matches("db-clean") {
        let database = parse_db()?;
        let (token, miner_url) = parse_miner()?;
        let mut options = MySqlConnectOptions::from_str(&database)?;
        options.disable_statement_logging();
        let pool = MySqlPool::connect_with(options).await?;
        // let _ = tool::transfer_old::parse_worker_online(&pool).await?;
        // let _ = tool::transfer_old::transfer_old_from_sqlite(&pool).await?;

        let client = Client::new();
        let _ = tool::clean::clean_db_task_not_really_running(&pool, &client, &miner_url, &token).await?;
        // let _ = db::repair_wrong_p1_sector(&pool).await?;
    }

    if let Some(ref matches) = matches.subcommand_matches("sector") {
        let database = parse_db()?;
        let (token, miner_url) = parse_miner()?;
        let client = Client::new();

        let mut options = MySqlConnectOptions::from_str(&database)?;
        options.disable_statement_logging();
        let pool = MySqlPool::connect_with(options).await?;

        // let _ = tool::find::find_sector_in_storage(&client, &miner_url, &token).await?;
        // let _ = tool::sector::correct_sector_table_by_jobs(&pool, &client, &miner_url, &token).await?;
        let _ = tool::sector::correct_remove_sector_state(&pool, &client, &miner_url, &token).await?;
    }

    if let Some(ref matches) = matches.subcommand_matches("compare") {
        let database = parse_db()?;
        let (token, miner_url) = parse_miner()?;
        let client = Client::new();

        let mut options = MySqlConnectOptions::from_str(&database)?;
        options.disable_statement_logging();
        let pool = MySqlPool::connect_with(options).await?;

        let _ = tool::compare::compare_jobs_and_database(&pool, &client, &miner_url, &token).await?;
    }

    if let Some(ref matches) = matches.subcommand_matches("tools") {
        let _ = tool::string::handle_string().await?;
    }
    Ok(())
}

fn parse_db() -> Result<String> {
    let db_url = dotenv::var("FIL_DATABASE_URL");
    if let Err(err) = db_url {
        log::error!("env 'FIL_DATABASE_URL' for database url not found.");
        return Err(anyhow::Error::from(err));
    }
    let db_url = db_url.unwrap();
    let vec: Vec<&str> = db_url.split("@").collect();
    let login = vec[0];
    let left = vec[1];

    let login: Vec<&str> = login.split(":").collect();
    let username = login[0];
    let password = login[1];

    let server = find_string(left, '(', ')');
    let db_name = find_string(left, '/', '?');

    let server: Vec<&str> = server.split(":").collect();
    let db_host = server[0];
    let db_port = server[1];
    let db_port = u16::from_str(db_port).unwrap();
    let db = format!(
        "mysql://{}:{}@{}:{}/{}",
        username, password, db_host, db_port, db_name
    );
    log::info!("database: {}", db);
    Ok(db)
}

fn parse_miner() -> Result<(String, String)> {
    let mut store_home = dotenv::var("LOTUS_MINER_PATH");
    if store_home.is_err() {
        store_home = dotenv::var("LOTUS_STORAGE_PATH");
    }
    if let Err(err) = store_home {
        log::error!(
            "both env 'LOTUS_MINER_PATH' and 'LOTUS_MINER_PATH' not found. can't get miner repo."
        );
        return Err(anyhow::Error::from(err));
    }
    let store_home = store_home.unwrap();
    println!("store home {}", store_home);
    let store_home = PathBuf::from(store_home);
    let api_path = store_home.join("api");
    let api = read_file_to_string(api_path)?;
    let token_path = store_home.join("token");
    let token = read_file_to_string(token_path)?;

    let api_info: Vec<&str> = api.split("/").collect();
    if api_info.len() != 6 {
        return Err(anyhow::Error::msg("api format not right."));
    };

    let api_ip = api_info[2];
    let api_port = api_info[4];
    let miner_url = format!("http://{}:{}/rpc/v0", api_ip, api_port);

    log::info!("token: {}", token);
    log::info!("miner: {}", miner_url);
    Ok((token, miner_url))
}

fn read_file_to_string(path: PathBuf) -> Result<String> {
    let mut file = File::open(path)?;
    let mut buf = String::new();
    let _ = file.read_to_string(&mut buf)?;
    Ok(buf)
}

fn find_string(input: &str, start: char, stop: char) -> String {
    let mut out = String::new();
    let mut find = false;
    for c in input.chars() {
        if c == start {
            find = true;
            continue;
        }
        if !find {
            continue;
        }
        if c == stop {
            break;
        } else {
            out.push(c);
        }
    }
    out
}